#include "git-compat-util.h"
#include "sigchain.h"

#define SIGCHAIN_MAX_SIGNALS 32

struct sigchain_signal {
	sigchain_fun *old;
	int n;
	int alloc;
};
static struct sigchain_signal signals[SIGCHAIN_MAX_SIGNALS];

/**
 * Checks that the given signal number is valid.
 * Aborts if it is out of range.
 */
static void check_signum(int sig)
{
	if (sig < 1 || sig >= SIGCHAIN_MAX_SIGNALS)
		BUG("signal out of range: %d", sig);
}

/**
 * Pushes a signal handler onto the sigchain for the given signal number.
 *
 * @param sig The signal number to push the handler for.
 * @param f The signal handler function.
 * @return 0 on success, -1 on failure.
 */
int sigchain_push(int sig, sigchain_fun f)
{
	struct sigchain_signal *s = signals + sig;
	check_signum(sig);

	ALLOC_GROW(s->old, s->n + 1, s->alloc);
	s->old[s->n] = signal(sig, f);
	if (s->old[s->n] == SIG_ERR)
		return -1;
	s->n++;
	return 0;
}

/**
 * Removes the top signal handler for the given signal number from the
 * sigchain, restoring the previously installed handler.
 *
 * @param sig The signal number to pop the handler for.
 * @return 0 on success, -1 on failure.
 */
int sigchain_pop(int sig)
{
	struct sigchain_signal *s = signals + sig;
	check_signum(sig);
	if (s->n < 1)
		return 0;

	if (signal(sig, s->old[s->n - 1]) == SIG_ERR)
		return -1;
	s->n--;
	return 0;
}

/**
 * Pushes common signal handlers onto the sigchain.
 * The given handler function will be invoked for SIGINT,
 * SIGHUP, SIGTERM, SIGQUIT and SIGPIPE.
 */
void sigchain_push_common(sigchain_fun f)
{
	sigchain_push(SIGINT, f);
	sigchain_push(SIGHUP, f);
	sigchain_push(SIGTERM, f);
	sigchain_push(SIGQUIT, f);
	sigchain_push(SIGPIPE, f);
}

/**
 * Pops the common signal handlers previously pushed by
 * sigchain_push_common(). Restores the original signal handlers.
 */
void sigchain_pop_common(void)
{
	sigchain_pop(SIGPIPE);
	sigchain_pop(SIGQUIT);
	sigchain_pop(SIGTERM);
	sigchain_pop(SIGHUP);
	sigchain_pop(SIGINT);
}
